package cn.janescott.framework.dubbo.filter;

import cn.janescott.framework.core.exception.MyException;
import cn.janescott.framework.core.exception.MyExceptionEnums;
import cn.janescott.framework.core.utils.JsonUtils;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;

import java.lang.reflect.Method;

/**
 * dubbo服务端异常处理器
 * {@link 'http://www.cnblogs.com/mumuxinfei/p/9231310.html'}
 * {@link 'https://www.jianshu.com/p/1e2245fe5ea6'}
 * {@link 'https://www.jianshu.com/p/4956d8ac58d3'}
 * config @ application.properties or application.yml with spring.dubbo.provider.filter=exceptionFilter,-exception
 * this filter is only useful for provider-side to handle exception listed blow.
 * this filter is cooperate with {@see MyResultFilter}
 * 返回结果格式:
 * 异常标记#~#异常序列化#~#异常消息
 *
 * @author scott
 * @date 2019/2/19
 * @see IllegalArgumentException 非法参数异常
 * @see BindException spring数据绑定异常
 * @see MyException 自定义捕捉异常
 * // * @see MyResultFilter 统一dubbo调用返回结果对象
 * @see com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec#encodeResponseData 服务端结果编码时，会判断是否有异常，有异常则只获取异常；没有异常则只获取值，不会处理附件里面的内容
 * @see ProtocolFilterWrapper
 * @see ExtensionLoader
 */
@Activate(group = "provider", before = {"exception"})
public class MyExceptionFilter implements Filter {
    /**
     * 分隔符
     */
    public static final String SEPARATOR = "#~#";

    /**
     * 手动处理异常标记
     */
    public static final String HANDLED_EXCEPTION = "HANDLED_EXCEPTION";

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();
                    //非运行时异常
                    if (!(exception instanceof RuntimeException) && exception instanceof Exception) {
                        //@Valid 注解抛出的异常
                        if (exception instanceof BindException) {
                            FieldError fieldError = ((BindException) exception).getFieldError();
                            String errorMsg = fieldError != null ? fieldError.getDefaultMessage() : MyExceptionEnums.ILLEGAL_ARGUMENT.getMessage();
                            return handle(null, errorMsg);
                        } else {
                            return result;
                        }
                    } else {
                        // 直接抛出接口上声明的异常
                        try {
                            Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
                            Class<?>[] exceptionClasses = method.getExceptionTypes();
                            for (Class<?> exceptionClass : exceptionClasses) {
                                if (exception.getClass().equals(exceptionClass)) {
                                    return result;
                                }
                            }
                        } catch (NoSuchMethodException e) {
                            return result;
                        }

                        //这里可以定义系统异常基类
                        if (exception instanceof MyException) {
                            return handle(exception, null);
                        }
                        //Assert.xxx抛出的异常
                        if (exception instanceof IllegalArgumentException) {
                            return handle(exception, null);
                        }

                        this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
                        String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
                        String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
                        if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) {
                            String className = exception.getClass().getName();
                            if (!className.startsWith("java.") && !className.startsWith("javax.")) {
                                return (exception instanceof RpcException ? result : new RpcResult(new RuntimeException(StringUtils.toString(exception))));
                            } else {
                                return result;
                            }
                        } else {
                            return result;
                        }
                    }
                } catch (Throwable var12) {
                    this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12);
                    return result;
                }
            } else {
                return result;
            }
        } catch (RuntimeException var13) {
            this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var13.getClass().getName() + ": " + var13.getMessage(), var13);
            throw var13;
        }
    }

    /**
     * 处理异常
     * 抛出运行时异常
     * 将实际异常放进attachment中
     * 异常标记#~#异常序列化#~#异常消息
     *
     * @param th
     * @return
     */
    private RpcResult handle(Throwable th, String message) {
        RpcResult result = new RpcResult();
        /* 异常标记位 */
        String errMsg = HANDLED_EXCEPTION;
        errMsg += SEPARATOR;
        /* 异常内容 */
        if (message == null || message.isEmpty()) {
            errMsg += JsonUtils.toJson(th);
        } else {
            errMsg += "";
        }
        errMsg += SEPARATOR;
        /* 异常消息 */
        if (message != null && !message.isEmpty()) {
            errMsg += message;
        } else {
            errMsg += "";
        }
        result.setException(new RuntimeException(errMsg));
        return result;
    }

}
